package org.zjvis.datascience.service.graph;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Joiner;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.zjvis.datascience.common.dto.TaskInstanceDTO;
import org.zjvis.datascience.common.dto.graph.GraphActionDTO;
import org.zjvis.datascience.common.dto.graph.GraphDTO;
import org.zjvis.datascience.common.enums.ActionEnum;
import org.zjvis.datascience.common.util.CollectionUtil;
import org.zjvis.datascience.common.vo.graph.*;
import org.zjvis.datascience.service.TColumnService;
import org.zjvis.datascience.service.dataprovider.GPDataProvider;
import org.zjvis.datascience.service.mapper.GraphActionMapper;

import javax.annotation.PreDestroy;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @description GraphActionService
 * @date 2021-12-29
 */
@Service
public class GraphActionService {

    private final static Logger logger = LoggerFactory.getLogger("GraphActionService");

    @Autowired
    private GraphActionMapper graphActionMapper;

    @Autowired
    private GraphActionStackWrapper graphActionStackWrapper;

    @Autowired
    private GPDataProvider gpDataProvider;

    @Lazy
    @Autowired
    private GraphService graphService;

    @Autowired
    private TColumnService tColumnService;

    @Autowired
    private JanusGraphEmbedService janusGraphEmbedService;

    @Autowired
    private GraphAnalysisService graphAnalysisService;

    public boolean save(GraphActionDTO graphActionDTO) {
        return graphActionMapper.save(graphActionDTO);
    }

    public void delete(Long id) {
        graphActionMapper.delete(id);
    }

    public void deleteByGraphId(Long graphId) {
        graphActionMapper.deleteByGraphId(graphId);
    }

    public boolean update(GraphActionDTO graphActionDTO) {
        return graphActionMapper.update(graphActionDTO);
    }

    public GraphActionDTO queryById(Long id) {
        return graphActionMapper.queryById(id);
    }

    public List<GraphActionDTO> queryBygraphId(Long graphId) {
        return graphActionMapper.queryByGraphId(graphId);
    }

    public List<GraphActionDTO> queryLatestActionByGraphId(Long graphId, Long limitNum) {
        return graphActionMapper.queryLatestActionByGraphId(graphId, limitNum);
    }

    public boolean initStackForGraphId(Long graphId) {
        return graphActionStackWrapper.initStackForProjectId(graphId, this);
    }

    /**
     * 添加action
     * @param actionType 操作类型
     * @param ctx1 操作前现场
     * @param ctx2 操作后现场
     * @return
     */
    public boolean addActionForGraph(ActionEnum actionType, GraphVO ctx1, GraphVO ctx2) {
        return this.addActionForGraph(actionType, ctx1, ctx2, null);
    }

    public boolean addActionForGraph(ActionEnum actionType, GraphVO ctx1, GraphVO ctx2, GraphVO ctx3) {
        return addActionForGraph(actionType, ctx1, ctx2, ctx3, new JSONObject());
    }

    public boolean addActionForGraph(ActionEnum actionType, GraphVO ctx1, GraphVO ctx2, GraphVO ctx3, JSONObject context) {
        if (ctx1 == null) {
            return false;
        }
        this.initStackForGraphId(ctx1.getId());
        GraphActionDTO actionDTO = new GraphActionDTO();
        actionDTO.setActionType(actionType.label());
        actionDTO.setProjectId(ctx1.getProjectId());
        actionDTO.setPipelineId(ctx1.getPipelineId());
        actionDTO.setTaskId(ctx1.getTaskId());
        actionDTO.setGraphId(ctx1.getId());
        actionDTO.setUserId(ctx1.getUserId());
        JSONObject data = new JSONObject();
        data.put("ctx1", ctx1);
        data.put("ctx2", ctx2);
        data.put("ctx3", ctx3);
        context.put("data", data);
        actionDTO.setContext(context.toJSONString());
        boolean flag = this.save(actionDTO);
        GraphActionDeStack actionStack = graphActionStackWrapper
                .getActionDeStackByGraphId(ctx1.getId());
        actionStack.addUndoStack(actionDTO);
        compressedStack(actionStack);
        clearRedoStack(actionStack);
        return flag;
    }
//
//    public boolean addCleanAction() {
//        return flag;
//    }


    public GraphActionDTO undo(Long graphId) {
        this.initStackForGraphId(graphId);
        GraphActionDeStack actionDeStack = graphActionStackWrapper.getActionDeStackByGraphId(graphId);
        GraphActionDTO undoAction = actionDeStack.undo();
        if (undoAction == null) {
            logger.error("undo empty!!!");
            return null;
        }
        boolean flag = this.recoverHandler(undoAction, true);
        if (!flag) {
            logger.error("recover fail!!!");
            actionDeStack.redo();
        }
        return undoAction;
    }

    public GraphActionDTO redo(Long graphId) {
        this.initStackForGraphId(graphId);
        GraphActionDeStack actionDeStack = graphActionStackWrapper.getActionDeStackByGraphId(graphId);
        GraphActionDTO redoAction = actionDeStack.redo();
        if (redoAction == null) {
            logger.error("redo empty!!!");
            return null;
        }
        boolean flag = this.recoverHandler(redoAction, false);
        if (!flag) {
            logger.error("recover fail!!!");
            actionDeStack.undo();
        }
        return redoAction;
    }

    /**
     * redo和undo 实际操作入口，根据不同的actionType执行不通的恢复处理逻辑
     */
    public boolean recoverHandler(GraphActionDTO action, boolean isUndo) {
        if (action == null) {
            return false;
        }
        String actionType = action.getActionType();
        boolean flag = false;
        if (actionType.equals(ActionEnum.UPDATE.label()) || actionType.equals(ActionEnum.EDGE_CONF.label())) {
            // 更新场景
            flag = this.updateHandler(action, isUndo);
        } else if (actionType.equals(ActionEnum.ADD.label())) {
            flag = this.addHandler(action, isUndo);
        } else if (actionType.equals(ActionEnum.DELETE.label())) {
            flag = this.deleteHandler(action, isUndo);
        } else if (actionType.equals(ActionEnum.CLEAN_ADD.label())) {
            //添加清洗记录，撤销需要删除记录
            flag = this.cleanAddHandler(action, isUndo);
        } else if (actionType.equals(ActionEnum.CLEAN_DELETE.label())) {
            //删除清洗记录，撤销需要添加记录
            flag = this.cleanAddHandler(action, !isUndo);
        } else if (actionType.equals(ActionEnum.CLEAN_UPDATE.label())) {
            flag = this.cleanUpdateHandler(action, isUndo);
        } else if (actionType.equals(ActionEnum.GRAPH_FILTER.label())) {
            flag = this.graphFilterHandler(action, isUndo);
        }
        if (flag) {
            if (isUndo) {
                // if undo, then delete action from action table
                if (this.queryById(action.getId()) != null) {
                    try {
                        this.delete(action.getId());
                    } catch (Exception e) {
                        logger.error(e.getMessage());
                        return false;
                    }
                }
            } else {
                //重做，则将action加回到撤销表中
                save(action);
            }
        }
        return flag;
    }

    public boolean addHandler(GraphActionDTO action, boolean isUndo) {
        GraphActionVO actionVO = action.view();
        JSONObject context = actionVO.getContextObj();

        if (isUndo) {
            // 逆向操作删除
            this.batchDeleteContext(context);
        } else {
            // 重做添加
            this.batchAddContext(context);
        }
        action.setContext(context.toJSONString());
        return true;
    }

    public boolean deleteHandler(GraphActionDTO action, boolean isUndo) {
        GraphActionVO actionVO = action.view();
        JSONObject context = actionVO.getContextObj();

        if (isUndo) {
            // 逆向操作添加
            this.batchAddContext(context);
        } else {
            // 重做删除
            this.batchDeleteContext(context);
        }
        action.setContext(context.toJSONString());
        return true;
    }

    public boolean updateHandler(GraphActionDTO action, boolean isUndo) {
        GraphActionVO actionVO = action.view();
        JSONObject context = actionVO.getContextObj();
        JSONObject data = context.getJSONObject("data");
        GraphVO ctx1 = data.getObject("ctx1", GraphVO.class);
        GraphVO ctx2 = data.getObject("ctx2", GraphVO.class);
        GraphVO ctx;
        if (isUndo) {
            //逆向恢复成ctx1
            ctx = ctx1;
        } else {
            //重做成ctx2
            ctx = ctx2;
        }
        List<CategoryVO> categories = ctx.getCategories();
        List<EdgeVO> edges = ctx.getEdges();
        List<NodeVO> nodes = ctx.getNodes();
        List<LinkVO> links = ctx.getLinks();

        for (CategoryVO categoryVO: categories) {
            graphService.updateCategory(ctx.getId(), categoryVO, true);
        }
        for (EdgeVO edgeVO: edges) {
            graphService.updateEdge(ctx.getId(), edgeVO);
        }
        if (nodes.size() != 0 || links.size() != 0 || ctx.getActionContext() != null) {
            graphService.batchUpdate(ctx.getId(), nodes, links, null, null, null, ctx.getActionContext());
        }
        return true;
    }

    public void batchDeleteContext(JSONObject context) {
        JSONObject data = context.getJSONObject("data");
        GraphVO graphVO = data.getObject("ctx1", GraphVO.class);
        List<String> cids = graphVO.getCategories().stream().map(CategoryVO::getId).collect(Collectors.toList());
        List<String> eids = graphVO.getEdges().stream().map(EdgeVO::getId).collect(Collectors.toList());
        if (cids.size() != 0 || eids.size() != 0) {
            graphService.batchDeleteCategoriesAndEdges(graphVO.getId(), cids, eids, null, context);
        }
        List<String> nids = graphVO.getNodes().stream().map(NodeVO::getId).collect(Collectors.toList());
        List<String> lids = graphVO.getLinks().stream().map(LinkVO::getId).collect(Collectors.toList());
        if (nids.size() != 0 || lids.size() != 0) {
            graphAnalysisService.batchDeleteNodesAndLinks(graphVO.getId(), nids, lids, null);
        }
    }

    public void batchAddContext(JSONObject context) {
        JSONObject data = context.getJSONObject("data");
        GraphVO graphVO = data.getObject("ctx1", GraphVO.class);
        List<CategoryVO> categories = graphVO.getCategories();
        List<EdgeVO> edges = graphVO.getEdges();
        List<NodeVO> nodes = graphVO.getNodes();
        List<LinkVO> links = graphVO.getLinks();
        Long graphId = graphVO.getId();

        if (categories.size() != 0 || edges.size() != 0) {
            for (CategoryVO categoryVO: categories) {
                graphService.updateCategoryWithoutCheck(graphId, categoryVO);
            }
            for (EdgeVO edgeVO: edges) {
                graphService.updateEdgeWithoutCheck(graphId, edgeVO);
            }
            //加回清洗记录
            JSONObject jsonActions = (JSONObject)context.remove("cleanActions");
            context.remove("views");
            if (CollectionUtil.isNotEmpty(jsonActions)) {
                this.addCleanActionsRecord(jsonActions);
            }
        }
        if (nodes.size() != 0 || links.size() != 0) {
            Map<String, String> idMapUpdate = new HashMap();
            for (NodeVO node: nodes) {
                Vertex v = janusGraphEmbedService.createVertex(node, node.getCategoryId(), graphId);
                idMapUpdate.put(v.id().toString(), node.getId());
            }
            for (LinkVO link: links) {
                janusGraphEmbedService.createEdge(null, null, link, link.getEdgeId(), graphId);
            }
            janusGraphEmbedService.commit();
            graphService.batchUpdate(graphId, nodes, links, null, null, null, null, idMapUpdate);
        }
    }

    public void addCleanActionsRecord(JSONObject jsonActions) {
        Set<String> keys = jsonActions.keySet();
        for (String key : keys) {
            List<TaskInstanceDTO> actions = jsonActions.getJSONArray(key)
                    .toJavaList(TaskInstanceDTO.class);
            actions.forEach(action -> tColumnService.saveAction(action));
        }
    }

    /**
     * 栈中的条数超过限制，删除
     */
    public void compressedStack(GraphActionDeStack actionStack) {
        int out = actionStack.outLimit();
        if (out > 0) {
            for (int i = 0; i < out; i++) {
                GraphActionDTO actionDTO = actionStack.removeUndoStack();
                removeAction(actionDTO);
                this.delete(actionDTO.getId());
            }
        }
    }

    /**
     * 清空重做栈
     */
    public void clearRedoStack(GraphActionDeStack actionStack) {
        while (actionStack.redoStackSize() > 0) {
            GraphActionDTO redoAction = actionStack.removeRedoStack();
            removeAction(redoAction);
        }
    }

    /**
     * 删除action相关表
     */
    public void removeAction(GraphActionDTO actionDTO) {
        JSONObject context = JSONObject.parseObject(actionDTO.getContext());
        JSONArray tables = context.getJSONArray("tables");
        JSONArray views = context.getJSONArray("views");
        if (CollectionUtil.isNotEmpty(tables)) {
            gpDataProvider.dropRedundantTables(Joiner.on(",").join(tables), false);
        }
        if (CollectionUtil.isNotEmpty(views)) {
            gpDataProvider.dropRedundantTables(Joiner.on(",").join(views), true);
        }
    }

    /**
     * 在系统停止前清空redo栈,并删除表
     */
    @PreDestroy
    public void destroyRedoStack() {
        logger.info("system stopping... clear graph redoStack");
        Map<Long, GraphActionDeStack> stackWrapper = graphActionStackWrapper.getStackWrapper();
        Set<Long> keys = stackWrapper.keySet();
        if (CollectionUtil.isEmpty(keys)) {
            return;
        }
        for (Long key : keys) {
            GraphActionDeStack remove = stackWrapper.remove(key);
            clearRedoStack(remove);
        }
    }

    public JSONObject actionElementAna(GraphActionDTO graphActionDTO, boolean isUndo) {
        //适用于图分析仅有update和delete操作
        //适用于图分析过滤器应用操作
        JSONObject obj = new JSONObject();
        Long gid = graphActionDTO.getGraphId();
        String actionType = graphActionDTO.getActionType();
        obj.put("actionType", actionType);
        JSONObject context = JSONObject.parseObject(graphActionDTO.getContext());
        JSONObject contextData = context.getJSONObject("data");
        JSONObject ctx;
        if (actionType.equals(ActionEnum.DELETE.label())) {
            ctx = contextData.getJSONObject("ctx1");
        }
        else if (actionType.equals(ActionEnum.GRAPH_FILTER.label())) {
            return obj;
        }
        else {
            if (isUndo) {
                ctx = contextData.getJSONObject("ctx1");
            } else {
                ctx = contextData.getJSONObject("ctx2");
            }
            obj.put("actionContext", ctx.get("actionContext"));
        }

        List<NodeVO> nodes = ctx.getJSONArray("nodes").toJavaList(NodeVO.class);
        List<LinkVO> links = ctx.getJSONArray("links").toJavaList(LinkVO.class);
        obj.put("actionNodesIds", nodes.stream().map(NodeVO::getId).collect(Collectors.toList()));
        obj.put("actionLinksIds", links.stream().map(LinkVO::getId).collect(Collectors.toList()));
        if (!actionType.equals(ActionEnum.DELETE.label()) || isUndo) {
            obj.put("actionNodes", nodes);
            obj.put("actionLinks", links);
        }
        return obj;
    }

    public JSONObject actionElement(GraphActionDTO graphActionDTO) {
        //返回操作的元素id与对应id的现场
        JSONObject obj = new JSONObject();
        Long gid = graphActionDTO.getGraphId();
        String actionType = graphActionDTO.getActionType();
        obj.put("actionType", actionType);
        List<CategoryVO> actionCategories = new ArrayList<>();
        List<EdgeVO> actionEdges = new ArrayList<>();
        if (actionType.equals(ActionEnum.CLEAN_ADD.label())
                || actionType.equals(ActionEnum.CLEAN_DELETE.label())
                || actionType.equals(ActionEnum.CLEAN_UPDATE.label())) {
            //清洗数据结构有区别，单独获取;每次清洗操作仅对应一个节点
            JSONArray ctx1 = JSONObject.parseObject(graphActionDTO.getContext()).getJSONObject("data").getJSONArray("ctx1");
            if (ctx1.size() > 0) {
                CategoryVO category = ctx1.getJSONObject(0).getObject("category",CategoryVO.class);
                actionCategories.add(category);
            }
        } else {
            JSONObject contextObj = JSONObject.parseObject(graphActionDTO.getContext()).getJSONObject("data").getJSONObject("ctx1");
            actionCategories = contextObj.getJSONArray("categories").toJavaList(CategoryVO.class);
            actionEdges = contextObj.getJSONArray("edges").toJavaList(EdgeVO.class);
        }

        List<String> actionCategoryId = actionCategories.stream().map(CategoryVO::getId).collect(Collectors.toList());
        List<String> actionEdgeId = actionEdges.stream().map(EdgeVO::getId).collect(Collectors.toList());

        obj.put("actionCategoryIds", actionCategoryId);
        obj.put("actionEdgeIds", actionEdgeId);


        GraphVO graphVO = graphService.queryById(gid).leftView();
        List<CategoryVO> categories = graphVO.getCategories();
        List<EdgeVO> edges = graphVO.getEdges();

        actionCategories.clear();
        actionEdges.clear();
        actionCategories.addAll(
                categories.stream().filter(c-> actionCategoryId.contains(c.getId())).collect(Collectors.toList())
        );

        actionEdges.addAll(
                edges.stream().filter(e-> actionEdgeId.contains(e.getId())).collect(Collectors.toList())
        );

        obj.put("actionCategories", actionCategories);
        obj.put("actionEdges", actionEdges);
        return obj;
    }

    /**
     * 清洗操作添加到撤销栈
     * @param actionType 操作的类型
     * @param ctx1 旧现场
     * @param ctx2 新现场
     */
    public boolean addCleanAction(ActionEnum actionType, List<JSONObject> ctx1,
                                  List<JSONObject> ctx2, Long graphId) {
        TaskInstanceDTO instance = ctx1.get(0).toJavaObject(TaskInstanceDTO.class);
        GraphDTO graph = graphService.queryById(graphId);
        this.initStackForGraphId(graphId);
        GraphActionDTO actionDTO = new GraphActionDTO();
        actionDTO.setActionType(actionType.label());
        actionDTO.setProjectId(graph.getProjectId());
        actionDTO.setPipelineId(graph.getPipelineId());
        actionDTO.setTaskId(graph.getTaskId());
        actionDTO.setGraphId(graphId);
        actionDTO.setUserId(graph.getUserId());

        JSONObject context = new JSONObject();
        JSONObject data = new JSONObject();
        data.put("ctx1", ctx1);
        data.put("ctx2", ctx2);
        context.put("data", data);
        actionDTO.setContext(context.toJSONString());
        boolean flag = this.save(actionDTO);
        GraphActionDeStack actionStack = graphActionStackWrapper
                .getActionDeStackByGraphId(graphId);
        actionStack.addUndoStack(actionDTO);
        compressedStack(actionStack);
//        clearRedoStack(actionStack);
        return flag;
    }

    public boolean cleanAddHandler(GraphActionDTO action, boolean isUndo) {
        GraphActionVO actionVO = action.view();
        JSONObject contextObj = actionVO.getContextObj();
        JSONObject item = contextObj.getJSONObject("data").getJSONArray("ctx1").getJSONObject(0);
        int index = item.getInteger("index");
        TaskInstanceDTO cleanAction = item.toJavaObject(TaskInstanceDTO.class);
        List<TaskInstanceDTO> actions = graphService.queryActionByParentIdAndOrder(cleanAction.getParentId());
        graphService.clearActions(actions, cleanAction.getParentId());
        if (isUndo) {
            //撤销,删除清洗记录
            actions.remove(index);
            index--;
        } else {
            //重做，添加清洗记录
            actions.add(index, cleanAction);
        }
        graphService.rebuildActions(actions);
        //更新节点信息
        CategoryVO currentCategory = graphService.getCategoryByCId(action.getGraphId(), cleanAction.getParentId());
        graphService.updateCategoryInfo(action.getGraphId(), currentCategory, actions, index);
        return true;
    }

    private boolean cleanUpdateHandler(GraphActionDTO action, boolean isUndo) {
        GraphActionVO actionVO = action.view();
        JSONObject contextObj = actionVO.getContextObj();
        JSONObject data = contextObj.getJSONObject("data");
        JSONObject item;
        if (isUndo) {
            //撤销，恢复成ctx1
            item = data.getJSONArray("ctx1").getJSONObject(0);
        } else {
            //重做，恢复成ctx2
            item = data.getJSONArray("ctx2").getJSONObject(0);
        }
        int index = item.getInteger("index");
        TaskInstanceDTO cleanAction = item.toJavaObject(TaskInstanceDTO.class);
        List<TaskInstanceDTO> actions = graphService.queryActionByParentIdAndOrder(cleanAction.getParentId());
        graphService.clearActions(actions, cleanAction.getParentId());
        actions.remove(index);
        actions.add(index, cleanAction);
        graphService.rebuildActions(actions);
        //更新节点信息
        CategoryVO currentCategory = graphService.getCategoryByCId(action.getGraphId(), cleanAction.getParentId());
        graphService.updateCategoryInfo(action.getGraphId(), currentCategory, actions, index);
        return true;
    }

    public boolean graphFilterHandler(GraphActionDTO action, boolean isUndo) {
        GraphActionVO actionVO = action.view();
        JSONObject context = actionVO.getContextObj();
        JSONObject data = context.getJSONObject("data");
        GraphVO ctx1 = data.getObject("ctx1", GraphVO.class);
        GraphVO ctx2 = data.getObject("ctx2", GraphVO.class);
        GraphVO ctx;
        if (isUndo) {
            ctx = ctx1;
        } else {
            ctx = ctx2;
        }
        graphAnalysisService.updateFilterInstance(action.getGraphId(), ctx.getFilterInstanceId());
        return true;
    }
}


